<?php

/* View.php
 *
 * (C) 2012, StaySimple CMS team.
 *
 * This file is part of StaySimple.
 * http://code.google.com/p/stay-simple-cms/
 *
 * StaySimple is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * StaySimple is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with StaySimple.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * The <em>View</em> class executes the template code, it his in this context 
 * that any template will run and when inside a theme/template <em>$this</em> 
 * will always reference a <em>View</em> instance.
 * 
 * Most of the methods in this class are intended to be used by themes or templates
 * and are convinience methods that give easier access various system information.
 */
class View {

    private $layout;
    private $scripts;
    private $styles;
    private $inits;
    private $data;
    private $theme;
    private $themePath;
    private $parcials;
    private $controller;

    public function __construct($controller, $theme, $layout = '//layouts/admin') {
        $this->controller = $controller;
        $this->layout = $layout;
        $this->theme = $theme;
        $this->themePath = APPROOT . '/themes/' . $this->theme . '/';
        $this->scripts = array();
        $this->styles = array();
        $this->inits = array();
        $this->data = array();
        $this->parcials = array();
    }

    public function __get($name) {
        return (isset($this->data[$name]) ? $this->data[$name] : null);
    }

    /**
     * This method will construct an URL that is properly configured for the current 
     * system, taking care of placing or hidding the <em>index.php</em> file or
     * deciding if and what path to add regarding administration sections.
     * 
     * Whenever a link needs to be created that should not be static, this is the 
     * method to use. If you don't use this method, the links you create will be 
     * static and will break the system if it is moved to another host or domain.
     * 
     * @param string $path The base path to use when creating the URL.
     * @return string The constructed URL.
     */
    public function createUrl($path) {
        $base = StaySimpleCMS::app()->getSiteUrl() . (Config::getInstance()->get('site.hideindex') ? '' : 'index.php/');
        if (StaySimpleCMS::app()->getLocation() == StaySimpleCMS::$ADMIN) {
            $base .= Config::getInstance()->get('system.path') . '/';
        }

        return $base . $path;
    }

    /**
     * Executes the <em>Plugin::getOutput()</em> method and returns the result 
     * for a given plugin.
     * 
     * @param string $outputID The ID of the plugin to call.
     * @return string The output returned by the plugin.
     */
    public function includePluginOutput($outputID) {
        return Plugin::getOutput($outputID, $this);
    }

    /**
     * Includes a file in the current context.
     * 
     * @param string $name The name of the file to include, without the <em>.php</em>
     * extension.
     */
    public function includeTemplateFile($name) {
        $name = $this->themePath . $name . '.php';
        if (is_file($name))
            include $name;
    }

    /**
     * Registers a script file to be added to this view. The script file can be 
     * included from the <strong>theme</strong> folder, the <strong>core/ui</strong>
     * folder or the plugin folder, if it's a plugin script.
     * 
     * In all cases the file should be called with a path relative to the base 
     * folder: if it's a theme the path is relative to the theme's folder; if 
     * it's a core file the path is relative to the <em>ui</em> folder in the 
     * system's core; if it's a plugin, the path is relative to the <em>plugins</em>
     * folder <strong>and not</strong> to the plugin's folder.
     * 
     * There is no way for the <em>Resource</em> controller to know what is the 
     * pluging that is being executed/requested.
     * 
     * Also, to register a core or plugin scritp, the $asset parameter needs to 
     * be given.
     * 
     * @param string $script The relative path to the file.
     * @param string $asset If the file is a core file, <em>'ui'</em> is the 
     * correct asset, if the file is from a plugin, the asset parameter should 
     * be <em>'plugin'</em>.
     */
    public function registerScript($script, $asset = null) {
        $important = false;
        if ($script[0] === '!') {
            $script = str_replace('!', '', $script);
            $important = true;
        }

        if (!isset($this->scripts[$script])) {

            if ($important) {
                $this->scripts = array_merge(array("$script" => array('name' => $script)), $this->scripts);
            } else {
                $this->scripts[$script]['name'] = $script;
            }
            if (!$asset) {
                $asset = $this->theme;
            }

            $this->scripts[$script]['asset'] = $asset;
        }
    }

    /**
     * Registers a CSS file in this view.
     * 
     * Registering a CSS file is pretty much like registering a JS script file. 
     * Please see the <em>registerScript</em> method's docs for more info.
     * 
     * @param string $style The relative path to the style file.
     * @param string $asset The asset base string, if any.
     * @param string $media The 'media' attribute for the <em>link</em> tag. It
     * defaults to <em>screen</em>.
     */
    public function registerStyle($style, $asset = null, $media = 'screen') {
        $important = false;
        if ($style[0] === '!') {
            $style = str_replace('!', '', $style);
            $important = true;
        }

        if (!isset($this->styles[$style])) {
            if ($important) {
                $this->styles = array_merge(array("$style" => array('name' => $style)), $this->styles);
            } else {
                $this->styles[$style]['name'] = $style;
            }
            if (!$asset) {
                $asset = $this->theme;
            }

            $this->styles[$style]['asset'] = $asset;
            $this->styles[$style]['media'] = $media;
        }
    }

    /**
     * Registers a JS script code fragment. These code fragments are used to place
     * JS code inside the <em>script</em> tags in this view.
     * 
     * The script can either be placed inside the jQuery function executed on the 
     * document.ready event or be placed outside the jQuery functions. The first 
     * option is indicated for init jQuery scripts, the later for special functions 
     * or code.
     * 
     * This method is useful to the administration section since jQuery won't be 
     * available in the site themes unless the theme's author adds it.
     * 
     * @param string $script A string with JS code. <em>NOT A FILE</em>.
     * @param boolean $onready If true (default), the code will be added to the 
     * init function that is run by jQuery when the document is ready, otherwise 
     * it will be added to the end of the <em>script</em> section outside the 
     * jQuery function.
     */
    public function registerInitScript($script, $onready = true) {
        if ($onready) {
            $this->inits['ready'][] = $script;
        } else {
            $this->inits['alone'][] = $script;
        }
    }

    /**
     * Returns the site URL, with the correct <em>index.php</em> part hidden if 
     * needed.
     * 
     * @return string The complete URL.
     */
    public function getSiteFullURL() {
        return StaySimpleCMS::app()->getSiteUrl() . (Config::getInstance()->get('site.hideindex') ? '' : 'index.php/');
    }

    /**
     * Generates the HTML needed to include all the registered CSS files.
     * 
     * @return string HTML with the links for all registered CSS files.
     */
    public function getStyleSection() {
        $base = $this->getSiteFullURL() . 'resource/get/';

        ob_start();
        foreach ($this->styles as $style) {
            echo "\n", '<link rel="stylesheet" type="text/css" media="', $style['media'],
            '" href="', $base, $style['asset'], '/', $style['name'], '" />';
        }
        echo "\n";

        return ob_get_clean();
    }

    /**
     * Generates the HTML needed to include all the registered JS files.
     * 
     * @return string HTML with the script tags for all registered JS files.
     */
    public function getScriptSection() {
        $base = $this->getSiteFullURL() . 'resource/get/';
        ob_start();
        foreach ($this->scripts as $script) {
            echo "\n", '<script type="text/javascript" src="', $base, $script['asset'],
            '/', $script['name'], '"></script>';
        }
        echo "\n";

        return ob_get_clean();
    }

    public function getInitScriptSection() {
        ob_start();
        if (count($this->inits)) {
            echo "\n", '<script type="text/javascript">';
            if (isset($this->inits['ready'])) {
                echo "\n$(document).ready(function() {";
                foreach ($this->inits['ready'] as $script) {
                    echo "\n", $script;
                }
                echo "\n});\n";
            }

            if (isset($this->inits['alone'])) {
                foreach ($this->inits['alone'] as $script) {
                    echo "\n", $script;
                }
                echo "\n\n";
            }

            echo '</script>';
        }

        return ob_get_clean();
    }

    public function getContentSection() {
        ob_start();
        foreach ($this->parcials as $parcial) {
            echo $parcial;
        }

        return ob_get_clean();
    }

    /**
     * Returns header section with all the meta tags for the current page. The 
     * code is created using the page's attributes for the author, the meta 
     * description and the meta keywords.
     * 
     * @return string The HTML code with all the meta tags.
     */
    public function getHeaderSection() {
        ob_start();

        echo '<meta name="author" content="', $this->page->getAuthor(), '" />', "\n";
        echo '<meta name="description" content="', $this->page->getMetaDescription(), '" />', "\n";
        echo '<meta name="keywords" content="', $this->page->getMetaKeywords(), '" />', "\n";

        return ob_get_clean();
    }

    /**
     * Returns the main menu.
     * 
     * @param array $options
     * 
     * @return string The menu.
     */
    public function getMainMenuSection($options = array()) {
        $beforeText = isset($options['beforeText']) ? $options['beforeText'] : '';
        $afterText = isset($options['afterText']) ? $options['afterText'] : '';
        $afterActive = isset($options['afterActive']) ? $options['afterActive'] : '</a>';
        $id = isset($options['id']) ? $options['id'] : 'mainnav';
        $class = isset($options['class']) ? ' class="' . $options['class'] . '"' : '';

        ob_start();

        echo '<ul id="', $id, '"', $class, '>';
        foreach ($this->pagemanager->getMenuPages() as $entry) {
            if (($this->page->getName() == $entry->getName())) {
                echo '<li>',
                (isset($options['beforeActive']) ? $options['beforeActive'] : ('<a href="' . $this->createUrl($entry->getName()) . '" class="current">'))
                , $entry->getMenuTitle(), $afterActive;
            } else {
                echo '<li><a href="', $this->createUrl($entry->getName()), '">', $beforeText, $entry->getMenuTitle(), $afterText, '</a></li>', "\n";
            }
        }
        echo '</ul>';

        return ob_get_clean();
    }

    /**
     * Creates the secundary menu with the child or sister pages for the current 
     * page.
     * 
     * @param array $options
     * @return string 
     */
    public function getSecundaryMenuSection($options = array()) {
        $wkpage = $this->page;

        $beforeText = isset($options['beforeText']) ? $options['beforeText'] : '';
        $afterText = isset($options['afterText']) ? $options['afterText'] : '';
        $afterActive = isset($options['afterActive']) ? $options['afterActive'] : '</a>';
        $id = isset($options['id']) ? $options['id'] : 'subnav';
        $class = isset($options['class']) ? ' class="' . $options['class'] . '"' : '';

        if ($this->page->getParent() != '') {
            $wkpage = PageManager::loadPage($this->page->getParent());
        }

        if ($wkpage->hasChildren()) {
            ob_start();
            echo '<ul id="', $id, '"', $class, '>';
            foreach ($wkpage->getChildren() as $child) {
                if ($child->getName() == $this->page->getName()) {
                    echo '<li>',
                    (isset($options['beforeActive']) ? $options['beforeActive'] : ('<a href="' . $child->getParent() . '/' . $child->getName() . '" class="current-child">'))
                    , $child->getTitle(), $afterActive;
                } else {
                    echo '<li><a href="', $this->createUrl($child->getName()), '">', $beforeText, $child->getTitle(), $afterText, '</a></li>', "\n";
                }
            }
            echo '</ul>';

            return ob_get_clean();
        }

        return '';
    }

    public function getMainMenuAnchors($separator = '&nbsp;') {
        ob_start();

        $entries = array();
        foreach ($this->pagemanager->getMenuPages() as $entry) {
            $entries[] = '<a href="' . $this->createUrl($entry->getName()) . '">' . $entry->getMenuTitle() . '</a>';
        }
        echo implode($separator, $entries);

        return ob_get_clean();
    }

    public function getSiteTitle() {
        return Config::getInstance()->get('site.name');
    }

    public function getAssetLink($asset, $base = null) {
        if (!$base) {
            $base = $this->theme;
        }
        return $this->getSiteFullURL() . 'resource/get/' . $base . '/' . $asset;
    }

    public function getDownloadLink($file) {
        return $this->getSiteFullURL() . 'resource/file/' . $file;
    }

    public function getContactEmail() {
        return Config::getInstance()->get('system.email');
    }

    public function getStatySimpleFooterCredits() {
        return ucfirst($this->t('STAY_GENERAL_USING'))
                . ' <a href="http://www.staysimple.net">StaySimple CMS '
                . StaySimpleCMS::app()->getVersion() . ' </a>';
    }

    public function renderPlugins($hook) {
        return Plugin::getOutput($hook, $this);
    }

    public function render($view, $data = array(), $return = false) {
        if (empty($view)) {
            throw new Exception('Invalid view.');
        }

        if (is_object($data)) {
            $data = get_object_vars($data);
        } else if (!is_array($data)) {
            $data = array($data => $data);
        }
        $this->data = array_merge($this->data, $data);

        ob_start();
        if (is_file($view)) {
            include $view;
        } else {
            $file = $this->themePath . $view . '.php';
            if (is_file($file)) {
                include $file;
            }
        }

        if ($return) {
            return ob_get_clean();
        }
        $this->parcials[] = ob_get_clean();
        $this->output();
    }

    public function output() {
        $output = '';
        $path = null;
        if (strpos($this->layout, '//layouts') === 0) {
            $name = explode('//layouts/', $this->layout);
            $path = APPROOT . '/themes/layouts/' . $name[1] . '.php';
        } else {
            $path = $this->themePath . $this->layout . '.php';
        }

        if (is_file($path)) {
            ob_start();
            include $path;
            $output = ob_get_clean();
        }

        echo $output;
    }

    public function getMessages() {
        $messages = array();

        while (($m = $this->controller->popMessage()) != null) {
            $messages[] = $m;
        }

        return $messages;
    }

    /**
     * Provides translation strings for a given key.
     * This method is nothing more than a wrapper for <em>getTranslatedString</em>
     * from <em>StaySimpleCMS</em> class.
     * 
     * @param string $key The key to use when looking for string translations.
     * @param array $params Any parameter to replace in the translated text.
     * 
     * @return string The translated string or the key if an invalid key was given.
     * 
     * @see StaySimpleCMS::getTranslatedString()
     */
    public function t($key, $params = array()) {
        return StaySimpleCMS::app()->getTranslatedString($key, $params);
    }

    public function getSystemLanguage() {
        return Config::getInstance()->get('system.locale.language');
    }

    public function getAllPublishedPages() {
        return $this->pagemanager->getPublishedPages();
    }

    public function getShortDateFormat() {
        return Config::getInstance()->get('system.locale.shortdateformat');
    }

    public function getLongDateFormat() {
        return Config::getInstance()->get('system.locale.longdateformat');
    }

    public function getTimeFormat() {
        return Config::getInstance()->get('system.locale.timeformat');
    }

}
